CS231 第七讲 训练神经网络(下)
课程视频地址:https://study.163.com/courses-search?keyword=CS231
课程主页:http://cs231n.stanford.edu/2017/
这一讲继续介绍训练神经网络的一些技巧。
更好的优化
之前我们介绍了随机梯度下降法,但是这个方法有一些问题——如果损失在某个方向降低很快,另一个方向降低很慢,那么图像如下
如果从红点出发,利用随机梯度下降法产生的结果非常震荡,这就会严重影响效率
另一个问题是,随机梯度下降很容易陷入局部最优点和马鞍点
还有一个问题是,随机梯度下降法很容易受到噪声干扰,这会让优化速度减慢
下面介绍几种处理上述问题的优化方法。
Momentum
第一种方法是Momentum,其更新公式如下
伪代码如下
vx = 0
while True:
dx = compute_gradient(x)
vx = rho * vx + dx
x -= learning_rate * vx
Momentum更新可以理解为球从山谷滚下来的过程,假设球位于某个点$x_t$,$\nabla f(x_t)$可以理解为此处的加速度,首先利用$v_{t+1} = \rho v_t + \nabla f(x_t)$更新速度,然后速度回根据$x_{t+1}= x_t -\alpha v_{t+1}$更新球的位置。超参数$\rho$常取$0.9$,效果如下
Nesterov Momentum
Nesterov Momentum和Momentum唯一的不同之处在于其计算的梯度为下一个点的梯度,其更新公式如下
为了让形式更简洁,作如下变换
那么上述更新公式可以改写为
伪代码如下
dx = compute_gradient(x)
old_v = v
v = rho * v - learning_rate * dx
x += -rho * old_v + (1 + rho) * v
Nesterov Momentum一般效果比Momentum更好
AdaGrad
AdaGrad是为了处理之前描述的第一个问题,采用的方法是将梯度归一化,伪代码如下
grad_squared = 0
while True:
dx = compute_gradient(x)
grad_squared += dx * dx
x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)
这个方法有一个问题,当迭代次数越来越多时,grad_squared会越来越大,每次更新的步长就会越来越小,这样就容易陷入局部最优的情况,解决方法是后面介绍的RMSProp。
RMSProp
RMSProp是对AdaGrad的改进,这里增加了一个衰减率,伪代码如下
grad_squared = 0
while True:
dx = compute_gradient(x)
grad_squared = decay_rate * grad_squared + (1 - decay_rate) * dx * dx
x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)
效果如下
Adam
Adam是将Momentum和RMSProp结合,伪代码如下
first_moment = 0
second_moment = 0
for t in range(num_iterations):
dx = compute_gradient(x)
first_moment = beta1 * first_moment + (1 - beta1) * dx
second_moment = beta2 * second_moment + (1 - beta2) * dx * dx
first_unbias = first_moment / (1 - beta1 ** t)
second_unbias = second_moment / (1 - beta2 ** t)
x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7)
其中倒数2,3行的步骤是为了处理first_moment和second_moment一开始都接近于$0$的问题,这里默认的超参数为
效果如下
学习率
上面介绍的几种优化方法都有学习率这个参数,之前介绍的方法中学习率都是固定的,实际中随时间衰减的学习率效果更好,一般有如下两种常见的衰减学习率:
指数衰减学习率
$1/t$衰减学习率
二阶优化
之前介绍的方法都是一阶优化方法,实际中还有二阶优化方法,其原理是基于泰勒展开
更新公式如下
上述方法被称为牛顿法,该方法的好处在于没有超参数,但是实际中几乎不用,因为计算逆矩阵需要非常大的计算量。实际中,一般利用BGFS或者L-BFGS来近似计算逆矩阵。在实际中,很少使用二阶优化,一般默认的优化器为Adam。
模型集成
模型集成是指训练几个独立的模型,在测试的时候平均他们的结果,这一般能提高$2\%$的效果。在神经网络中,一个简单的方法是计算参数的滑动平均值
while True:
data_batch = dataset.sample_data_batch()
loss = network.forward(data_batch)
dx = network.backward()
x += - learning_rate * dx
x_test = 0.995*x_text + 0.005*x
正则化
Dropout
模型集成可以提升整体效果,那么该如何提升单个模型的效果呢?由之前介绍的内容可知,我们应该使用正则化,神经网络中比较常用的正则化方法为Dropout,该方法的思路为让神经元随机失活,这样就可以减少有效的神经元数量
伪代码如下
""" Vanilla Dropout: Not recommended implementation (see notes below) """
p = 0.5 # probability of keeping a unit active. higher = less dropout
def train_step(X):
""" X contains the data """
# forward pass for example 3-layer neural network
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = np.random.rand(*H1.shape) < p # first dropout mask
H1 *= U1 # drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = np.random.rand(*H2.shape) < p # second dropout mask
H2 *= U2 # drop!
out = np.dot(W3, H2) + b3
# backward pass: compute gradients... (not shown)
# perform parameter update... (not shown)
def predict(X):
# ensembled forward pass
H1 = np.maximum(0, np.dot(W1, X) + b1) * p # NOTE: scale the activations
H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # NOTE: scale the activations
out = np.dot(W3, H2) + b3
上述方法需要注意一点:在预测的时候要乘以dropout概率$p$,这是因为假设输入为$x$,其期望输出为$px$,所以为了保持一致,预测时要乘以dropout概率$p$。这要会产生一个问题:预测时增加了运算量,一个改进方式如下
"""
Inverted Dropout: Recommended implementation example.
We drop and scale at train time and don't do anything at test time.
"""
p = 0.5 # probability of keeping a unit active. higher = less dropout
def train_step(X):
# forward pass for example 3-layer neural network
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = (np.random.rand(*H1.shape) < p) / p # first dropout mask. Notice /p!
H1 *= U1 # drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = (np.random.rand(*H2.shape) < p) / p # second dropout mask. Notice /p!
H2 *= U2 # drop!
out = np.dot(W3, H2) + b3
# backward pass: compute gradients... (not shown)
# perform parameter update... (not shown)
def predict(X):
# ensembled forward pass
H1 = np.maximum(0, np.dot(W1, X) + b1) # no scaling necessary
H2 = np.maximum(0, np.dot(W2, H1) + b2)
out = np.dot(W3, H2) + b3
数据扩充
还有一个常见的正则化方法为数据扩充——利用原始数据获得新的数据,图像数据中常用的方法为旋转,剪裁,修改颜色等等。
迁移学习
迁移学习解决了在小数据集上使用CNN的问题,其思路很简单,首先在大数据集上训练CNN,然后冻结某些层,在小数据集上训练最后几层,示意图如下
下图很好的概况的使用迁移学习的各种情形